local path = require 'pl.path'
local normpath = path.normpath
local defaultOutput = 'default_output_handler'

describe('Tests command-line interface', function()
  it('default options', function()
    local lpath = './src/?.lua;./src/?/?.lua;./src/?/init.lua'
    local cpath = path.is_windows and './csrc/?.dll;./csrc/?/?.dll;' or './csrc/?.so;./csrc/?/?.so;'
    local cli = require 'busted.modules.cli'({ standalone = false, output = defaultOutput })
    local args = cli:parse({})
    assert.is_equal(defaultOutput, args.o)
    assert.is_equal(defaultOutput, args.output)
    assert.is_same({'spec'}, args.ROOT)
    assert.is_equal('./', args.C)
    assert.is_equal('./', args.directory)
    assert.is_equal('/dev/urandom or os.time()', args.seed)
    assert.is_equal('en', args.lang)
    assert.is_equal(1, args['repeat'])
    assert.is_equal(lpath, args.m)
    assert.is_equal(lpath, args.lpath)
    assert.is_equal(cpath, args.cpath)
    assert.is_true(args['auto-insulate'])
    assert.is_true(args.k)
    assert.is_true(args['keep-going'])
    assert.is_true(args.R)
    assert.is_true(args['recursive'])
    assert.is_false(args.c)
    assert.is_false(args.coverage)
    assert.is_false(args.version)
    assert.is_false(args.v)
    assert.is_false(args.verbose)
    assert.is_false(args.l)
    assert.is_false(args.list)
    assert.is_false(args.lazy)
    assert.is_false(args.s)
    assert.is_false(args['enable-sound'])
    assert.is_false(args['suppress-pending'])
    assert.is_false(args['defer-print'])
    assert.is_nil(args.f)
    assert.is_nil(args['config-file'])
    assert.is_nil(args['coverage-config-file'])
    assert.is_nil(args.shuffle)
    assert.is_nil(args['shuffle-files'])
    assert.is_nil(args['shuffle-tests'])
    assert.is_nil(args.sort)
    assert.is_nil(args['sort-files'])
    assert.is_nil(args['sort-tests'])
    assert.is_nil(args.r)
    assert.is_nil(args.run)
    assert.is_nil(args.helper)
    assert.is_same({}, args.e)
    assert.is_same({'_spec'}, args.p)
    assert.is_same({'_spec'}, args.pattern)
    assert.is_same({}, args['exclude-pattern'])
    assert.is_same({}, args.t)
    assert.is_same({}, args.tags)
    assert.is_same({}, args['exclude-tags'])
    assert.is_same({}, args.filter)
    assert.is_same({}, args['filter-out'])
    assert.is_same({}, args.Xoutput)
    assert.is_same({}, args.Xhelper)
    assert.is_same({'lua', 'moonscript'}, args.loaders)
  end)

  it('standalone options disables ROOT and --pattern', function()
    local cli = require 'busted.modules.cli'({ standalone = true })
    local args = cli:parse({})
    assert.is_nil(args.ROOT)
    assert.is_same({}, args.p)
    assert.is_same({}, args.pattern)
    assert.is_same({}, args['exclude-pattern'])
  end)

  it('specify flags', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '-v', '--version', '--coverage', '--defer-print', '--suppress-pending' })
    assert.is_true(args.v)
    assert.is_true(args.verbose)
    assert.is_true(args.version)
    assert.is_true(args.coverage)
    assert.is_true(args['defer-print'])
    assert.is_true(args['suppress-pending'])
  end)

  it('specify more flags', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '-s', '--list', '-k', '--no-keep-going', '-R', '--no-recursive' })
    assert.is_true(args.s)
    assert.is_true(args['enable-sound'])
    assert.is_true(args.l)
    assert.is_true(args.list)
    assert.is_false(args['keep-going'])
    assert.is_false(args['recursive'])
  end)

  it('specify even more flags', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '--lazy', '--no-auto-insulate', '-k', '-R' })
    assert.is_true(args.lazy)
    assert.is_false(args['auto-insulate'])
    assert.is_true(args.k)
    assert.is_true(args['keep-going'])
    assert.is_true(args.R)
    assert.is_true(args.recursive)
  end)

  it('specify no-flags', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '--no-lazy', '--no-auto-insulate', '--no-coverage', '--no-verbose' })
    assert.is_false(args.lazy)
    assert.is_false(args['auto-insulate'])
    assert.is_false(args.coverage)
    assert.is_false(args.verbose)
  end)

  it('specify more no-flags', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '--no-enable-sound', '--no-suppress-pending', '--no-defer-print' })
    assert.is_false(args['enable-sound'])
    assert.is_false(args['suppress-pending'])
    assert.is_false(args['defer-print'])
  end)

  it('specify shuffle flags', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '--shuffle-files', '--shuffle-tests' })
    assert.is_nil(args.shuffle)
    assert.is_true(args['shuffle-files'])
    assert.is_true(args['shuffle-tests'])
  end)

  it('specify shuffle flag only', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '--shuffle', })
    assert.is_true(args.shuffle)
    assert.is_true(args['shuffle-files'])
    assert.is_true(args['shuffle-tests'])
  end)

  it('specify shuffle no-flags', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '--no-shuffle', '--no-shuffle-files', '--no-shuffle-tests' })
    assert.is_false(args.shuffle)
    assert.is_false(args['shuffle-files'])
    assert.is_false(args['shuffle-tests'])
  end)

  it('specify no-shuffle flag only', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '--no-shuffle', })
    assert.is_false(args.shuffle)
    assert.is_false(args['shuffle-files'])
    assert.is_false(args['shuffle-tests'])
  end)

  it('specify shuffle and no-shuffle flags', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '--shuffle', '--no-shuffle-files' })
    assert.is_true(args.shuffle)
    assert.is_false(args['shuffle-files'])
    assert.is_true(args['shuffle-tests'])
  end)

  it('specify sort flags', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '--sort-files', '--sort-tests' })
    assert.is_nil(args.sort)
    assert.is_true(args['sort-files'])
    assert.is_true(args['sort-tests'])
  end)

  it('specify sort flag only', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '--sort', })
    assert.is_true(args.sort)
    assert.is_true(args['sort-files'])
    assert.is_true(args['sort-tests'])
  end)

  it('specify sort no-flags', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '--no-sort', '--no-sort-files', '--no-sort-tests' })
    assert.is_false(args.sort)
    assert.is_false(args['sort-files'])
    assert.is_false(args['sort-tests'])
  end)

  it('specify no-sort flag only', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '--no-sort', })
    assert.is_false(args.sort)
    assert.is_false(args['sort-files'])
    assert.is_false(args['sort-tests'])
  end)

  it('specify sort and no-sort flags', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '--sort', '--no-sort-files' })
    assert.is_true(args.sort)
    assert.is_false(args['sort-files'])
    assert.is_true(args['sort-tests'])
  end)

  it('specify ROOT arg and --pattern', function()
    local cli = require 'busted.modules.cli'({ standalone = false })
    local args = cli:parse({ '-p', 'match_files', 'root_is_here' })
    assert.is_same({'root_is_here'}, args.ROOT)
    assert.is_same({'match_files'}, args.p)
    assert.is_same({'match_files'}, args.pattern)
  end)

  it('specify ROOT arg and --exclude-pattern', function()
    local cli = require 'busted.modules.cli'({ standalone = false })
    local args = cli:parse({ '--exclude-pattern', 'exclude_files', 'root_is_here' })
    assert.is_same({'root_is_here'}, args.ROOT)
    assert.is_same({'exclude_files'}, args['exclude-pattern'])
  end)

  it('specify multiple root paths', function()
    local cli = require 'busted.modules.cli'({ standalone = false })
    local args = cli:parse({'root1_path', 'root2_path', 'root3_path'})
    assert.is_same({'root1_path', 'root2_path', 'root3_path'}, args.ROOT)
  end)

  it('specify --directory', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '--directory=/path/to/dir' })
    assert.is_equal(normpath('/path/to/dir'), args.C)
    assert.is_equal(normpath('/path/to/dir'), args.directory)
  end)

  it('specify --directory multiple times', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '--directory=/path/to', '-C', 'dir', '--directory=subdir' })
    assert.is_equal(normpath('/path/to/dir/subdir'), args.C)
    assert.is_equal(normpath('/path/to/dir/subdir'), args.directory)
  end)

  it('specify --directory multiple times with multiple roots', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '--directory=/path/to', '-C', 'dir', '--directory=/new/path' })
    assert.is_equal(normpath('/new/path'), args.C)
    assert.is_equal(normpath('/new/path'), args.directory)
  end)

  it('specify --run', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '--run=task' })
    assert.is_equal('task', args.r)
    assert.is_equal('task', args.run)
  end)

  it('specify --lang', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '--lang=fr' })
    assert.is_equal('fr', args.lang)
  end)

  it('specify --repeat', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '--repeat=23' })
    assert.is_equal(23, args['repeat'])
  end)

  it('specify output library', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '-o', 'output_handler', '-Xoutput', '--flag,-f', '-Xoutput', '--opt=val' })
    assert.is_equal('output_handler', args.o)
    assert.is_equal('output_handler', args.output)
    assert.is_same({'--flag', '-f', '--opt=val'}, args.Xoutput)
  end)

  it('specify helper script', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '--helper=helper_script', '-Xhelper', '--flag,-f', '-Xhelper', '--opt=val'  })
    assert.is_equal('helper_script', args.helper)
    assert.is_same({'--flag', '-f', '--opt=val'}, args.Xhelper)
  end)

  it('specify --tags and --exclude-tags', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '--tags=tag1,tag2', '-t', 'tag3', '--exclude-tags=etag1', '--exclude-tags=etag2,etag3' })
    assert.is_same({'tag1', 'tag2', 'tag3'}, args.t)
    assert.is_same({'tag1', 'tag2', 'tag3'}, args.tags)
    assert.is_same({'etag1', 'etag2', 'etag3'}, args['exclude-tags'])
  end)

  it('specify --filter and --filter-out', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '--filter=_filt', '--filter-out=_filterout' })
    assert.is_same({'_filt'}, args.filter)
    assert.is_same({'_filterout'}, args['filter-out'])
  end)

  it('specify --filter and --filter-out multiple times', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '--filter=_filt1', '--filter=_filt2', '--filter-out=_filterout1', '--filter-out=_filterout2' })
    assert.is_same({'_filt1', '_filt2'}, args.filter)
    assert.is_same({'_filterout1', '_filterout2'}, args['filter-out'])
  end)

  it('specify --loaders', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '--loaders=load1,load2', '--loaders=load3' })
    assert.is_same({'load1', 'load2', 'load3'}, args.loaders)
  end)

  it('specify --lpath', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '-C', '/root', '--lpath=./path1/?.lua', '-m', './path2/?.lua' })
    assert.is_equal('./path1/?.lua;./path2/?.lua', args.m)
    assert.is_equal('./path1/?.lua;./path2/?.lua', args.lpath)
  end)

  it('specify --cpath', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '-C', '/croot', '--lpath=./path1/?.so', '-m', './path2/?.so' })
    assert.is_equal('./path1/?.so;./path2/?.so', args.m)
    assert.is_equal('./path1/?.so;./path2/?.so', args.lpath)
  end)

  it('specify -e statement', function()
    local cli = require 'busted.modules.cli'()
    local args = cli:parse({ '-e', 'statement1', '-e', 'statement2' })
    assert.is_same({'statement1', 'statement2'}, args.e)
  end)
end)

describe('Tests using .busted tasks', function()
  it('default options', function()
    local defaultOutput = 'default_output_handler'
    local lpath = './src/?.lua;./src/?/?.lua;./src/?/init.lua'
    local cpath = path.is_windows and './csrc/?.dll;./csrc/?/?.dll;' or './csrc/?.so;./csrc/?/?.so;'
    local cli = require 'busted.modules.cli'({ standalone = false, output = defaultOutput })
    local args = cli:parse({ '--directory=spec/.hidden' })
    assert.is_equal(defaultOutput, args.o)
    assert.is_equal(defaultOutput, args.output)
    assert.is_same({'specs'}, args.ROOT)
    assert.is_equal(normpath('spec/.hidden'), args.C)
    assert.is_equal(normpath('spec/.hidden'), args.directory)
    assert.is_equal('/dev/urandom or os.time()', args.seed)
    assert.is_equal('en', args.lang)
    assert.is_equal(1, args['repeat'])
    assert.is_equal(lpath, args.m)
    assert.is_equal(lpath, args.lpath)
    assert.is_equal(cpath, args.cpath)
    assert.is_true(args['auto-insulate'])
    assert.is_true(args.k)
    assert.is_true(args['keep-going'])
    assert.is_true(args.R)
    assert.is_true(args['recursive'])
    assert.is_false(args.c)
    assert.is_false(args.coverage)
    assert.is_false(args.version)
    assert.is_false(args.v)
    assert.is_false(args.verbose)
    assert.is_false(args.l)
    assert.is_false(args.list)
    assert.is_false(args.lazy)
    assert.is_false(args.s)
    assert.is_false(args['enable-sound'])
    assert.is_false(args['suppress-pending'])
    assert.is_false(args['defer-print'])
    assert.is_nil(args.f)
    assert.is_nil(args['config-file'])
    assert.is_nil(args['coverage-config-file'])
    assert.is_nil(args.shuffle)
    assert.is_nil(args['shuffle-files'])
    assert.is_nil(args['shuffle-tests'])
    assert.is_nil(args.sort)
    assert.is_nil(args['sort-files'])
    assert.is_nil(args['sort-tests'])
    assert.is_nil(args.r)
    assert.is_nil(args.run)
    assert.is_nil(args.helper)
    assert.is_same({}, args.e)
    assert.is_same({'_spec%.lua$'}, args.p)
    assert.is_same({'_spec%.lua$'}, args.pattern)
    assert.is_same({'_exclude'}, args['exclude-pattern'])
    assert.is_same({'tag11', 'tag22', 'tag33'}, args.t)
    assert.is_same({'tag11', 'tag22', 'tag33'}, args.tags)
    assert.is_same({'etag11', 'etag22', 'etag33'}, args['exclude-tags'])
    assert.is_same({'filt'}, args.filter)
    assert.is_same({'filt-out'}, args['filter-out'])
    assert.is_same({'-f', '--flag'}, args.Xoutput)
    assert.is_same({'-v', '--verbose'}, args.Xhelper)
    assert.is_same({'terra', 'moonscript'}, args.loaders)
  end)

  it('default options with --config-file option', function()
    local defaultOutput = 'default_output_handler'
    local lpath = './src/?.lua;./src/?/?.lua;./src/?/init.lua'
    local cpath = path.is_windows and './csrc/?.dll;./csrc/?/?.dll;' or './csrc/?.so;./csrc/?/?.so;'
    local cli = require 'busted.modules.cli'({ standalone = false, output = defaultOutput })
    local args = cli:parse({ '--config-file', 'spec/.hidden/.busted' })
    assert.is_equal(defaultOutput, args.o)
    assert.is_equal(defaultOutput, args.output)
    assert.is_same({'specs'}, args.ROOT)
    assert.is_equal('./', args.C)
    assert.is_equal('./', args.directory)
    assert.is_equal('spec/.hidden/.busted', args.f)
    assert.is_equal('spec/.hidden/.busted', args['config-file'])
    assert.is_equal('/dev/urandom or os.time()', args.seed)
    assert.is_equal('en', args.lang)
    assert.is_equal(1, args['repeat'])
    assert.is_equal(lpath, args.m)
    assert.is_equal(lpath, args.lpath)
    assert.is_equal(cpath, args.cpath)
    assert.is_true(args['auto-insulate'])
    assert.is_true(args.k)
    assert.is_true(args['keep-going'])
    assert.is_true(args.R)
    assert.is_true(args['recursive'])
    assert.is_false(args.c)
    assert.is_false(args.coverage)
    assert.is_false(args.version)
    assert.is_false(args.v)
    assert.is_false(args.verbose)
    assert.is_false(args.l)
    assert.is_false(args.list)
    assert.is_false(args.lazy)
    assert.is_false(args.s)
    assert.is_false(args['enable-sound'])
    assert.is_false(args['suppress-pending'])
    assert.is_false(args['defer-print'])
    assert.is_nil(args['coverage-config-file'])
    assert.is_nil(args.shuffle)
    assert.is_nil(args['shuffle-files'])
    assert.is_nil(args['shuffle-tests'])
    assert.is_nil(args.sort)
    assert.is_nil(args['sort-files'])
    assert.is_nil(args['sort-tests'])
    assert.is_nil(args.r)
    assert.is_nil(args.run)
    assert.is_nil(args.helper)
    assert.is_same({'_spec%.lua$'}, args.p)
    assert.is_same({'_spec%.lua$'}, args.pattern)
    assert.is_same({'_exclude'}, args['exclude-pattern'])
    assert.is_same({'tag11', 'tag22', 'tag33'}, args.t)
    assert.is_same({'tag11', 'tag22', 'tag33'}, args.tags)
    assert.is_same({'etag11', 'etag22', 'etag33'}, args['exclude-tags'])
    assert.is_same({'filt'}, args.filter)
    assert.is_same({'filt-out'}, args['filter-out'])
    assert.is_same({'-f', '--flag'}, args.Xoutput)
    assert.is_same({'-v', '--verbose'}, args.Xhelper)
    assert.is_same({'terra', 'moonscript'}, args.loaders)
  end)

  it('load configuration options', function()
    local cli = require 'busted.modules.cli'({ standalone = false, output = defaultOutput })
    local args = cli:parse({ '--directory=spec/.hidden', '--run=test' })
    assert.is_same({'_test1%.lua$', '_test2%.lua$'}, args.pattern)
    assert.is_same({'_exclude1', '_exclude2'}, args['exclude-pattern'])
    assert.is_same({'filt1', 'filt2'}, args.filter)
    assert.is_same({'filt1-out', 'filt2-out'}, args['filter-out'])
    assert.is_same({'tests'}, args.ROOT)
    assert.is_same({'test1', 'test2', 'test3'}, args.tags)
    assert.is_same({'etest1', 'etest2', 'etest3'}, args['exclude-tags'])
    assert.is_same({'-s','--sound'}, args.Xoutput)
    assert.is_same({'-t', '--print'}, args.Xhelper)
    assert.is_same({'lua', 'terra'}, args.loaders)
  end)

  it('load configuration options and override with command-line', function()
    local cli = require 'busted.modules.cli'({ standalone = false, output = defaultOutput })
    local args = cli:parse({ '--directory=spec/.hidden', '--run=test', '-t', 'tag1', '-p', 'patt', '--filter=fin', '--filter-out=fout', '--exclude-pattern', '', '--loaders=moonscript' })
    assert.is_same({'patt'}, args.pattern)
    assert.is_same({''}, args['exclude-pattern'])
    assert.is_same({'fin'}, args.filter)
    assert.is_same({'fout'}, args['filter-out'])
    assert.is_same({'tag1'}, args.tags)
    assert.is_same({'etest1', 'etest2', 'etest3'}, args['exclude-tags'])
    assert.is_same({'-s','--sound'}, args.Xoutput)
    assert.is_same({'-t', '--print'}, args.Xhelper)
    assert.is_same({'moonscript'}, args.loaders)
  end)

  it('detects error in configuration file', function()
    local cli = require 'busted.modules.cli'({ standalone = false, output = defaultOutput })
    cli:set_name('app')
    local args, err = cli:parse({ '--config-file=spec/.hidden/.busted_bad', '--run=test' })
    assert.is_nil(args)
    assert.has_match('^app: error: spec/.hidden/.busted_bad:8: ', err)
    assert.has_match("'doesnotexist'", err)
    assert.has_match("a nil value", err)
  end)

  it('detects invalid configuration file', function()
    local cli = require 'busted.modules.cli'({ standalone = false, output = defaultOutput })
    cli:set_name('myapp')
    local args, err = cli:parse({ '--config-file=spec/.hidden/.busted_empty' })
    assert.is_nil(args)
    assert.is_equal('myapp: error: .busted file does not return a table.', err)
  end)

  it('detects unknown/invalid task', function()
    local cli = require 'busted.modules.cli'({ standalone = false, output = defaultOutput })
    cli:set_name('appname')
    local args, err = cli:parse({ '--config-file=spec/.hidden/.busted', '--run=invalid' })
    assert.is_nil(args)
    assert.is_equal('appname: error: Task `invalid` not found, or not a table.', err)
  end)
end)

describe('Tests command-line parse errors', function()
  before_each(function()
    package.loaded['cliargs'] = nil
  end)

  it('with invalid --repeat value', function()
    local cli = require 'busted.modules.cli'()
    cli:set_name('myapp')
    local args, err = cli:parse({ '--repeat=abc'})
    assert.is_nil(args)
    assert.is_equal('myapp: error: argument to --repeat must be a number; re-run with --help for usage.', err)
  end)

  it('with same tag for --tags and --exclude-tags', function()
    local cli = require 'busted.modules.cli'()
    cli:set_name('myapp')
    local args, err = cli:parse({ '--tags=tag1', '--exclude-tags=tag1' })
    assert.is_nil(args)
    assert.is_equal('myapp: error: Cannot use --tags and --exclude-tags for the same tags', err)
  end)
end)
